package com.abu.notice.service.impl;

import cn.hutool.json.JSONUtil;
import com.abu.common.Resp;
import com.abu.common.exception.CRException;
import com.abu.notice.client.ManagerClient;
import com.abu.notice.config.mvc.LoginInterceptor;
import com.abu.notice.mapper.NoticeMapper;
import com.abu.notice.pojo.Notice;
import com.abu.notice.service.NoticeService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 阿布
 * @since 2021-04-29
 */
@Service
public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> implements NoticeService {

    @Autowired
    private NoticeMapper noticeMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ManagerClient managerClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    private static final String NOTICE_PREFIX = "NOTICE_PREFIX:";

    private static final String NOTICE_EXCHANGE = "NOTICE_EXCHANGE";

    private static final String NOTICE_ROUTING_KEY = "NOTICE_ROUTING_KEY";


    @Override
    public List<Notice> selectLatestNotice() {
        return this.noticeMapper.selectLatestFive();
    }

    @Override
    public Notice selectById(Long noticeId) {
        String prefix = NOTICE_PREFIX + noticeId;
//        1.查询缓存有没有
        String noticeDataStr = this.redisTemplate.opsForValue().get(prefix);
//        2.有的话直接返回
        if (StringUtils.isNotBlank(noticeDataStr)) {
            return JSONUtil.parseObj(noticeDataStr).toBean(Notice.class);
        }
//        3.没有的话加锁（防止击穿）
        RLock lock = redissonClient.getLock(noticeId.toString());
        lock.lock();
//        4.二次读缓存
        String secondNoticeDataStr = this.redisTemplate.opsForValue().get(prefix);
        if (StringUtils.isNotBlank(secondNoticeDataStr)) {
            Notice notice = JSONUtil.parseObj(secondNoticeDataStr).toBean(Notice.class);
//        4.1释放锁
            lock.unlock();
            return notice;
        }
//       5.没有数据则读数据库，将数据放入缓存中
        Notice notice = this.noticeMapper.selectOne(new QueryWrapper<Notice>().eq("id", noticeId).eq("is_publish", 1));
//        5.1将数据添加到缓存中
//        5.2不进行非空判断，将数据为空的数据也放入（防止穿透）
        String noticeStr = JSONUtil.parseObj(notice).toString();
        int expireTime = new Random().nextInt(10) + 1;
//        5.3指定时间失效时间为随机值（防止雪崩）
        this.redisTemplate.opsForValue().set(prefix, noticeStr, expireTime + 5, TimeUnit.MINUTES);
//         5.4释放锁
        lock.unlock();
        return notice;
    }

    @Override
    public HashMap<String, Object> selectByPage(Long page, Long size) {
        Long total = this.noticeMapper.selectNoticeTotalCount();
        HashMap<String, Object> returnData = new HashMap<>(4);
        List<Notice> noticeList = this.noticeMapper.selectByPage(page, size);
        returnData.put("total", total);
        returnData.put("noticeList", noticeList);
        return returnData;
    }

    @Override
    public List<Notice> selectByKey(String key) {
        QueryWrapper<Notice> noticeQueryWrapper = new QueryWrapper<Notice>().eq("is_publish", 1);
        if (StringUtils.isNotBlank(key)) {
            noticeQueryWrapper.like("title", key);
        }
        return this.noticeMapper.selectList(noticeQueryWrapper);
    }

    @Override
    public Boolean saveNotice(Notice notice) {
        this.setManagerNameByNoticeId(notice);
        return this.noticeMapper.insert(notice) == 1;
    }

    @Override
    public HashMap<String, Object> selectAllByPage(Long page, Long size) {
        Long total = this.noticeMapper.selectAllNoticeTotalCount();
        HashMap<String, Object> returnData = new HashMap<>(4);
        List<Notice> noticeList = this.noticeMapper.selectAllByPage(page, size);
        returnData.put("total", total);
        returnData.put("noticeList", noticeList);
        return returnData;
    }

    /**
     * 先更新数据库，再删除缓存
     *
     * @param notice
     * @return
     */
    @Override
    public boolean updateNoticeById(Notice notice) {
        Long noticeId = notice.getId();
//        1.先更新数据库数据
        String prefix = NOTICE_PREFIX + noticeId;
        this.setManagerNameByNoticeId(notice);
        boolean isRemoveDbDataFlag = this.updateById(notice);
//        2.使用异步延迟删除策略
        this.threadPoolExecutor.execute(() -> {
//            2.1暂停300毫秒，要保证删除旧数据的操作在读操作后执行
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            2.2删除缓存中数据
            boolean deleteFlag = this.redisTemplate.delete(prefix);
//            2.3第一次删除失败，将key发送到消息中间件中，尝试删除
            if (!deleteFlag) {
                this.rabbitTemplate.convertAndSend(NOTICE_EXCHANGE, NOTICE_ROUTING_KEY, noticeId);
            }
        });
//        3.删除缓存数据失败,将数据的id作为key，发送消息到消息队列中，尝试删除直至成功
        return isRemoveDbDataFlag;
    }


    @Override
    public List<Notice> selectAllMatched(String key) {
        return this.noticeMapper.selectList(new QueryWrapper<Notice>().like("title", key));
    }

    @Override
    public boolean updateNoticeStatusByNoticeId(Long id, Boolean status) {
//        1.先更新数据库
        UpdateWrapper<Notice> updateWrapper = new UpdateWrapper<Notice>().eq("id", id).set("is_publish", status);
//        2.使用异步延迟删除策略
        String prefix = NOTICE_PREFIX + id;
        this.threadPoolExecutor.execute(() -> {
//            2.1 延迟是保证写操作要在读操作之后执行，能够删除旧数据
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            2.2 执行删除缓存数据
            boolean deleteFlag = this.redisTemplate.delete(prefix);
            if (!deleteFlag) {
//            2.3 由于某种原因导致第一次删除缓存失败，提供一个保障重试机制，发送消息到消息对列中，尝试再次删除
                this.rabbitTemplate.convertAndSend(NOTICE_EXCHANGE, NOTICE_ROUTING_KEY, id);
            }

        });
        return this.noticeMapper.update(null, updateWrapper) == 1;
    }

    @Override
    public boolean removeNoticeById(Long noticeId) {
//        1.将缓存中数据删掉
        String prefix = NOTICE_PREFIX + noticeId;
        this.redisTemplate.delete(prefix);
//        2.删除数据库公告信息
        return this.noticeMapper.deleteById(noticeId) == 1;
    }


    private void setManagerNameByNoticeId(Notice notice) {
        String managerId = LoginInterceptor.getManagerId();
        Resp resp = this.managerClient.getManagerByReviewerId(managerId);
        Integer code = resp.getCode();
        String msg = resp.getMsg();
        if (code == 503) {
            throw new CRException(msg, code);
        }
        if (code == 404) {
            throw new CRException(msg, code);
        }
        LinkedHashMap managerMap = (LinkedHashMap) resp.getData().get("manager");
//            获取管理员名字
        String managerName = (String) managerMap.get("realName");
        notice.setEditor(managerName);
    }
}
